from dataclasses import dataclass
from typing import *

import streamlit as st

_T = TypeVar("_T")
Key: TypeAlias = Union[str, int]
WidgetCallback: TypeAlias = Callable[..., None]
LabelVisibility = Literal["visible", "hidden", "collapsed"]

@dataclass
class Component:
    """Base class for components """
    
    _wrapped = None
    _instance = None 
    
    _saveDesc: str = ""
    
    def _create(self):
        argMap: dict[str, Any] = {}
        for attr in dir(self):
            # private attribute
            if attr.startswith("_"): continue
            argMap[attr] = getattr(self, attr)
        
        self._instance = self._wrapped(**argMap) # type: ignore
        return self._instance
    
    def __matmul__(self, T: type[_T]) -> TypeGuard[_T]:
        return isinstance(self, T)

# ! streamlit.errors.StreamlitAPIException: `st.button()` can't be used in an `st.form()`.
# ! Refer to (https://docs.streamlit.io/library/api-reference/control-flow/st.form).
# @dataclass
# class Button(Component):
#     """ Wrapper of streamlit.button """
    
#     _wrapped = st.button
    
#     label: str = ""
#     key: Key | None = None
#     help: str | None = None
#     on_click: WidgetCallback | None = None
#     type: Literal['primary', 'secondary'] = "secondary"
#     disabled: bool = False
#     use_container_width: bool = False
    
#     def _handle(self): ...

@dataclass
class Checkbox(Component):
    """ Wrapper of streamlit.checkbox """
    
    _wrapped = st.checkbox
    
    label: str = ""
    value: bool = False
    key: Key | None = None
    help: str | None = None
    on_change: WidgetCallback | None = None
    disabled: bool = False
    label_visibility: LabelVisibility = "visible"
    
@dataclass
class SelectBox(Component):
    """ Wrapper of streamlit.selectbox """
    
    _wrapped = st.selectbox
    
    label: str = ""
    options: list[str] | None = None
    index: int | None = 0
    format_func: Callable[[Any], Any] = str
    key: Key | None = None
    help: str | None = None
    on_change: WidgetCallback | None = None
    placeholder: str = "Choose an option"
    disabled: bool = False
    label_visibility: LabelVisibility = "visible"
    
@dataclass
class MultiSelect(Component):
    """ Wrapper of streamlit.multiselect """
    
    _wrapped = st.multiselect
    
    label: str = ""
    options: list[str] | None = None
    default: Any | None = None
    format_func: Callable[[Any], Any] = str
    key: Key | None = None
    help: str | None = None
    on_change: WidgetCallback | None = None
    max_selections: int | None = None
    placeholder: str = "Choose an option"
    disabled: bool = False
    label_visibility: LabelVisibility = "visible"
    
@dataclass
class TextInput(Component):
    """ Wrapper of streamlit.text_input """
    
    _wrapped = st.text_input
    
    label: str = ""
    value: str | None = ""
    max_chars: int | None = None
    key: Key | None = None
    type: Literal["default", "password"] = "default"
    help: str | None = None
    autocomplete: str | None = None
    on_change: WidgetCallback | None = None
    placeholder: str | None = None
    disabled: bool = False
    label_visibility: LabelVisibility = "visible"
    
@dataclass
class FileUploader(Component):
    """ Wrapper of streamlit.file_uploader """
    
    _wrapped = st.file_uploader

    label: str = ""
    type: str | Sequence[str] | None = None
    accept_multiple_files: bool = False
    key: Key | None = None
    help: str | None = None
    on_change: WidgetCallback | None = None
    disabled: bool = False
    label_visibility: LabelVisibility = "visible"
    
@dataclass
class FormSubmitButton(Component):
    """ Wrapper of streamlit.form_submit_button, especially,
    _create() method should be called explicitly outside of
    Form, within st.form statement.
    """
    
    _wrapped = st.form_submit_button
    
    label: str = "Submit"
    help: str | None = None
    on_click: WidgetCallback | None = None
    type: Literal['primary', 'secondary'] = "secondary"
    disabled: bool = False
    use_container_width: bool = False

@dataclass
class CompResultDesc:
    component: Component
    result: Any

CompResultHandler: TypeAlias = Callable[[list[CompResultDesc]], None]

class _Form:
    """The global form manager, singleton """
    
    def __init__(self) -> None:
        self.__components: list[Component] = []
        self.__handler: CompResultHandler | None = None
        self.__results: list[CompResultDesc] = []
        
    def add_component(self, component: Component) -> None:       
        self.__components.append(component)
        
    def add_components(self, components: list[Component]) -> None:     
        for comp in components:
            self.__components.append(comp)
        
    def remove_component(self, component: Component) -> None:       
        self.__components.remove(component)
        
    def remove_components(self, components: list[Component]) -> None:     
        for comp in components:
            self.__components.remove(comp)
            
    def set_handler(self, handler: CompResultHandler) -> None:
        self.__handler = handler
            
    def _create(self) -> list[CompResultDesc]:
        results = []
        for comp in self.__components:
            results.append(CompResultDesc(comp, comp._create()))
        self.__results = results
        return results
    
    def _handle(self) -> None:
        if self.__handler is None:
            return
        self.__handler(self.__results)

Form = _Form()
